/*
 * Copyright (c) China Telecom Cloud Technology Co., Ltd. 2024-2025. All rights reserved.
 */
#ifndef LOGGER_H
#define LOGGER_H

#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <queue>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <atomic>
#include <chrono>
#include <iomanip>
#include <ctime>
#include <memory>
#include <algorithm>
#include <filesystem>
#include <type_traits>
#include <utility>
#include <string_view>

#include <ConfigLoader.h>


#define LOGGER_ONE 1
#define LOGGER_TWO 2
#define LOGGER_THREE 3

namespace fs = std::filesystem;

enum class LogLevel { DEBUG, INFO, WARN, ERROR };


/**
 *! @brief 日志消息的内容
 * 
 */
struct LogMessage {
    // 日志级别
    LogLevel level;

    // 日志发生位于的代码文件
    std::string file;

    // 日志发生的函数名字
    std::string func;

    // 日志发生的代码行                                
    int line;

    // 日志内容                                   
    std::string message;

    // 日志时间戳                          
    std::chrono::system_clock::time_point timestamp;
};


class Logger {
public:
    static Logger &instance()
    {
        static Logger instance;
        return instance;
    }

    void init(const std::string &filepath = "", 
        size_t max_file_size = 10 * 1024 * 1024) {
        
        // DEFAULT_LOG_LEVEL 编译时宏替换
        // TODO(yangxianpku): 将日志界别设备运行时参数而非编译时采纳数
        const std::string LOG_LEVEL = ConfigLoader::get_config<std::string>(
                                        ConfigLoader::EnvVars::LOG_LEVEL, 
                                        DEFAULT_LOG_LEVEL 
                                    );
        set_level_from_string(LOG_LEVEL);

        max_file_size_ = max_file_size;

        if (!filepath.empty()) {
            file_sink_ = std::make_unique<FileSink>(filepath, max_file_size);
        }

        running_ = true;
        worker_ = std::thread(&Logger::process_messages, this);
    }

    void shutdown()
    {
        running_ = false;
        cv_.notify_all();
        if (worker_.joinable()) {
            worker_.join();
        }
        file_sink_.reset();
    }

    void set_level(LogLevel level)
    {
        level_ = level;
    }

    template <typename... Args>
    void log(LogLevel level, 
            const char *file, 
            const char *func, 
            int line, 
            Args &&...args) {
        if (should_skip(level)) 
            return;

        std::ostringstream oss;
        (oss << ... << std::forward<Args>(args));
        log_message(level, file, func, line, oss.str());
    }

    template <typename... Args>
    void log_fmt(LogLevel level, 
                const char *file, 
                const char *func, 
                int line, 
                std::string_view format, 
                Args &&...args) {
        if (should_skip(level)) 
            return;
        
        std::ostringstream oss;
        format_string(oss, format, std::forward<Args>(args)...);
        log_message(level, file, func, line, oss.str());
    }

private:
    class FileSink {
    public:
        FileSink(const std::string &base_path, size_t max_size)
            : base_path_(base_path),
              max_size_(max_size),
              current_size_(0)
        {
            roll_file();
        }

        void write(const std::string &message)
        {
            if (!file_.is_open())
                return;

            file_ << message << std::endl;
            current_size_ += message.size() + 1;
            if (current_size_ >= max_size_) {
                roll_file();
            }
        }

    private:
        void roll_file()
        {
            if (file_.is_open()) {
                file_.close();
            }

            auto now = std::chrono::system_clock::now();
            auto time_t = std::chrono::system_clock::to_time_t(now);
            std::tm tm;
            localtime_r(&time_t, &tm);
            char buffer[30];
            strftime(buffer, sizeof(buffer), "%Y%m%d_%H%M%S", &tm);

            std::string filename = base_path_ + "_" + buffer + ".log";
            file_.open(filename, std::ios::out | std::ios::app);
            current_size_ = 0;
        }

        std::ofstream file_;
        std::string base_path_;
        size_t max_size_;
        size_t current_size_;
    };

    bool should_skip(LogLevel level) const {
        return static_cast<int>(level) < static_cast<int>(level_);
    }

    void log_message(LogLevel level, 
                    const char *file, 
                    const char *func, 
                    int line, 
                    const std::string &message) {
        auto now = std::chrono::system_clock::now();
        {
            std::lock_guard<std::mutex> lock(queue_mutex_);
            message_queue_.push({ level, file, func, line, message, now });
        }
        cv_.notify_one();
    }

    static void format_string(std::ostringstream& oss, 
                            std::string_view format) {
        size_t start = 0;
        while (start < format.size()) {
            size_t open_brace = format.find('{', start);
            if (open_brace == std::string_view::npos) {
                oss << format.substr(start);
                break;
            }
            
            if (open_brace + 1 < format.size() && format[open_brace + 1] == '{') {
                oss << format.substr(start, open_brace - start) << '{';
                start = open_brace + LOGGER_TWO;
                continue;
            }
            
            oss << format.substr(start, open_brace - start) << '{';
            start = open_brace + 1;
        }
    }

    template <typename T, typename... Args>
    static void format_string(std::ostringstream& oss, 
                            std::string_view format, 
                            T&& value, 
                            Args&&... args) {
        size_t start = 0;
        while (start < format.size()) {
            size_t open_brace = format.find('{', start);
            
            if (open_brace == std::string_view::npos) {
                oss << format.substr(start);
                oss << value;
                int dummy[] = { (oss << std::forward<Args>(args), 0)... };
                (void)dummy;
                return;
            }
            
            if (open_brace + 1 < format.size() && format[open_brace + 1] == '{') {
                oss << format.substr(start, open_brace - start) << '{';
                start = open_brace + LOGGER_TWO;
                continue;
            }
            
            if (open_brace + 1 < format.size() && format[open_brace + 1] == '}') {
                oss << format.substr(start, open_brace - start);
                oss << value;
                format_string(oss, 
                            format.substr(open_brace + LOGGER_TWO), 
                            std::forward<Args>(args)...
                        );
                return;
            }
            
            oss << format.substr(start, open_brace - start) << '{';
            start = open_brace + 1;
        }
        
        oss << value;
        int dummy[] = { (oss << std::forward<Args>(args), 0)... };
        (void)dummy;
    }

    void set_level_from_string(const std::string &level_str)
    {
        std::string upper_str = level_str;
        std::transform(upper_str.begin(), 
                    upper_str.end(), 
                    upper_str.begin(),
                    [](unsigned char c) { 
                        return std::toupper(c); 
                    }
                );

        if (upper_str == "DEBUG")
            level_ = LogLevel::DEBUG;
        else if (upper_str == "INFO")
            level_ = LogLevel::INFO;
        else if (upper_str == "WARN")
            level_ = LogLevel::WARN;
        else if (upper_str == "ERROR")
            level_ = LogLevel::ERROR;
        else {
            level_ = LogLevel::INFO;
            log_fmt(LogLevel::ERROR, __FILE__, __func__, __LINE__, 
                   "Invalid LOG_LEVEL value: {}. Using default.", level_str);
        }
    }

    Logger() : running_(false), 
            level_(LogLevel::INFO), 
            max_file_size_(0) {}
    
    ~Logger() {
        if (running_)
            shutdown();
    }

    void process_messages() {
        while (running_ || !message_queue_.empty()) {
            std::unique_lock<std::mutex> lock(queue_mutex_);
            cv_.wait(lock, [this] { 
                                return !running_ || !message_queue_.empty(); 
                            }
                    );

            if (message_queue_.empty())
                continue;

            auto msg = message_queue_.front();
            message_queue_.pop();
            lock.unlock();

            std::string formatted = format_message(msg);
            console_output(formatted, msg.level);

            if (file_sink_) {
                file_sink_->write(formatted);
            }
        }
    }

    std::string format_message(const LogMessage &msg) {
        auto time_t = std::chrono::system_clock::to_time_t(msg.timestamp);
        std::tm tm;
        localtime_r(&time_t, &tm);
        char time_buf[20];
        strftime(time_buf, sizeof(time_buf), "%Y-%m-%d %H:%M:%S", &tm);

        auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(msg.timestamp.time_since_epoch()) % 1000;

        std::string filename = fs::path(msg.file).filename().string();

        std::ostringstream oss;
        oss << "[" << time_buf << "." << std::setfill('0') << std::setw(LOGGER_THREE) << ms.count() << "] "
            << "[" << level_to_string(msg.level) << "] "
            << "[" << filename << ":" << msg.line << ":" << msg.func << "] " << msg.message;
        return oss.str();
    }

    void console_output(const std::string &message, LogLevel level)
    {
        switch (level) {
            case LogLevel::DEBUG:
                std::cout << "\033[36m" << message << "\033[0m" << std::endl;
                break;
            case LogLevel::INFO:
                std::cout << "\033[32m" << message << "\033[0m" << std::endl;
                break;
            case LogLevel::WARN:
                std::cout << "\033[33m" << message << "\033[0m" << std::endl;
                break;
            case LogLevel::ERROR:
                std::cout << "\033[31;1m" << message << "\033[0m" << std::endl;
                break;
            default:
                std::cout << message << std::endl;
        }
    }

    std::string level_to_string(LogLevel level)
    {
        switch (level) {
            case LogLevel::DEBUG:
                return "DEBUG";
            case LogLevel::INFO:
                return "INFO";
            case LogLevel::WARN:
                return "WARN";
            case LogLevel::ERROR:
                return "ERROR";
            default:
                return "UNKNOWN";
        }
    }

    std::atomic<bool> running_;
    LogLevel level_;
    size_t max_file_size_;

    std::unique_ptr<FileSink> file_sink_;
    std::thread worker_;
    std::mutex queue_mutex_;
    std::condition_variable cv_;
    std::queue<LogMessage> message_queue_;
};

#define LOG(level, ...) \
    Logger::instance().log(level, __FILE__, __func__, __LINE__, __VA_ARGS__)

#define LOG_FMT(level, ...) \
    Logger::instance().log_fmt(level, __FILE__, __func__, __LINE__, __VA_ARGS__)

#define LOG_DEBUG(...) LOG(LogLevel::DEBUG, __VA_ARGS__)
#define LOG_INFO(...)  LOG(LogLevel::INFO, __VA_ARGS__)
#define LOG_WARN(...)  LOG(LogLevel::WARN, __VA_ARGS__)
#define LOG_ERROR(...) LOG(LogLevel::ERROR, __VA_ARGS__)

#define LOG_DEBUG_FMT(...) LOG_FMT(LogLevel::DEBUG, __VA_ARGS__)
#define LOG_INFO_FMT(...)  LOG_FMT(LogLevel::INFO, __VA_ARGS__)
#define LOG_WARN_FMT(...)  LOG_FMT(LogLevel::WARN, __VA_ARGS__)
#define LOG_ERROR_FMT(...) LOG_FMT(LogLevel::ERROR, __VA_ARGS__)

#endif  // LOGGER_H